/**
 * 
 */
package ro.dta.idbi.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import ro.dta.idbi.interfaces.ui.AbstractDiagram;
import ro.dta.idbi.interfaces.ui.IMainFrame;
import uk.ac.ic.doc.automed.reps.Schema;

import com.mxgraph.io.mxCodec;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxICell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;

/**
 * Integration strategy creator diagram
 * 
 * @author Tudor Dobrila
 * 
 */
public class StrategyDiagram extends AbstractDiagram {

	private static final long serialVersionUID = 7325023808085527606L;

	/**
	 * Strategy creator graph
	 */
	protected transient StrategyGraph graph;

	/**
	 * Hierarchical layout used to arrange the schemas in the diagram
	 */
	protected transient mxHierarchicalLayout layout;

	/**
	 * List of roots in the diagram
	 */
	protected transient List<Object> roots;

	/**
	 * String holding the previous value of a node in the graph, so it may be
	 * restored if the user enters an invalid name
	 */
	private transient String editCache;

	/**
	 * Strategy diagram class constructor
	 */
	public StrategyDiagram(final IMainFrame parentFrame, final mxGraphModel strategy) {
		super(parentFrame);
		setLayout(new BorderLayout());
		setBackground(Color.WHITE);

		roots = new ArrayList<Object>();

		graph = new StrategyGraph();
		graph.setModel(strategy);

		layout = new mxHierarchicalLayout(graph);
		layout.setFineTuning(true);
		layout.setLayoutFromSinks(true);

		graphComponent = new mxGraphComponent(graph);
		graphComponent.setConnectable(false);
		graphComponent.setEnterStopsCellEditing(true);
		graphComponent.getGraphControl().setName("graph");

		// Listen for right click
		graphComponent.getGraphControl().addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(final MouseEvent event) {
				final Object[] sel = graph.getSelectionCells();

				if (sel != null && sel.length > 1 && SwingUtilities.isRightMouseButton(event)) {
					final JPopupMenu groupPopup = new JPopupMenu();
					final JMenuItem combItem = new JMenuItem("Combine");
					combItem.addActionListener(new ActionListener() {
						@Override
						public void actionPerformed(final ActionEvent event) {
							combine(sel);

						}
					});
					groupPopup.add(combItem);

					groupPopup.show(event.getComponent(), event.getX(), event.getY());
				}
			}

		});

		graphComponent.addListener(mxEvent.START_EDITING, new mxIEventListener() {

			@Override
			public void invoke(final Object arg0, final mxEventObject arg1) {
				final mxCell cell = (mxCell) arg1.getProperty("cell");
				editCache = cell.getValue().toString();
			}
		});

		graphComponent.addListener(mxEvent.LABEL_CHANGED, new mxIEventListener() {

			@Override
			public void invoke(final Object arg0, final mxEventObject arg1) {
				final mxCell cell = (mxCell) arg1.getProperty("cell");
				final String value = cell.getValue().toString();
				if (schemaExists(value, cell) || Schema.exists(value)) {
					cell.setValue(editCache);
				}
				if (Schema.exists(value)) {
					JOptionPane.showMessageDialog(StrategyDiagram.this, "Schema " + value
							+ " already exists in the AutoMed repository.", "Error",
							JOptionPane.ERROR_MESSAGE);
				}
			}
		});

		graphComponent.getViewport().setBackground(Color.WHITE);
		add(graphComponent, BorderLayout.CENTER);
	}

	/**
	 * Add integration iteration (as a result of combining several schemas)
	 * 
	 * @param sel
	 */
	protected void combine(final Object[] sel) {
		final ArrayList<mxICell> vertices = new ArrayList<mxICell>();

		for (Object s : sel) {
			final mxCell cell = (mxCell) s;
			if (cell.isVertex()) {
				if (graph.getOutgoingEdges(cell).length > 0) {
					return;
				}
				vertices.add((mxICell) s);
			}
		}

		if (vertices.size() > 1) {
			graph.getModel().beginUpdate();

			// Construct the name of the new node
			StringBuilder builder = new StringBuilder();
			for (mxICell s : vertices) {
				builder.append(s.getValue()).append("_");
			}
			builder.setLength(builder.length() - 1);

			try {
				final Object obj = graph.insertVertex(graph.getDefaultParent(), null,
						builder.toString(), 0, 0, 150, 30, "INTERMEDIATE");
				roots.add(obj);

				for (mxICell s : vertices) {
					graph.insertEdge(graph.getDefaultParent(), "", "", s, obj);

					if (roots.contains(obj)) {
						roots.remove(obj);
					}
				}
			} finally {
				layout.execute(graph.getDefaultParent(), roots);
				graph.getModel().endUpdate();
			}
		}
	}

	/**
	 * Add a schema to the strategy diagram
	 * 
	 * @param schemaName
	 *            Name of the schema
	 */
	public void addSchema(final String schemaName) {
		if (schemaExists(schemaName)) {
			JOptionPane.showMessageDialog(this, "Schema " + schemaName
					+ " already exists in the strategy graph.", "Error", JOptionPane.ERROR_MESSAGE);
			return;
		}

		graph.getModel().beginUpdate();

		try {
			final Object obj = graph.insertVertex(graph.getDefaultParent(), null, schemaName, 0, 0,
					150, 30);
			roots.add(obj);
		} finally {
			layout.execute(graph.getDefaultParent(), roots);
			graph.getModel().endUpdate();
		}
	}

	/**
	 * Check if a schema exists in the strategy graph
	 * 
	 * @param schemaName
	 *            Schema to check for
	 * @return True if schema exists, false otherwise
	 */
	private boolean schemaExists(final String schemaName) {
		return schemaExists(schemaName, null);
	}

	/**
	 * Check if a schema exists in the strategy graph
	 * 
	 * @param schemaName
	 *            Schema to check for
	 * @param source
	 *            Object to skip during checking
	 * @return True if schema exists, false otherwise
	 */
	private boolean schemaExists(final String schemaName, final Object source) {
		final Object[] vertices = graph.getChildVertices(graph.getDefaultParent());
		boolean result = false;

		for (Object o : vertices) {
			final mxCell cell = (mxCell) o;

			if (cell.getValue().equals(schemaName) && cell != source) {
				result = true;
				break;
			}
		}

		return result;
	}

	/**
	 * Import diagram from XML file stored on disk
	 */
	public void importXML() {
		final mxGraphModel model = UiUtils.loadDiagramFromXml(parentFrame, this);

		if (model != null) {
			graph.setModel(model);

			final Collection<Object> cells = model.getCells().values();
			roots.clear();

			// Find the roots of the trees in the forest
			for (Object o : cells) {
				final mxCell cell = (mxCell) o;

				if (cell.isVertex() && mxGraphModel.getOutgoingEdges(model, cell).length == 0) {
					roots.add(cell);
				}
			}

			layout.execute(graph);
		}
	}

	/**
	 * Get the model in the graph describing the integration strategy
	 */
	public mxGraphModel getModel() {
		return (mxGraphModel) graph.getModel();
	}

	/**
	 * Check if all intermediate schemas have been given names
	 * 
	 * @return True if strategy is OK, false if errors exist in the definition
	 *         of the strategy
	 */
	public boolean verifyStrategy() {
		final Collection<Object> cells = ((mxGraphModel) graph.getModel()).getCells().values();
		boolean isOk = true;

		for (Object o : cells) {
			final mxCell cell = (mxCell) o;

			if (cell.isVertex() && cell.getStyle() != null
					&& cell.getStyle().equals("INTERMEDIATE")
					&& cell.getValue().equals("enter name")) {
				isOk = false;
				break;
			}
		}

		return isOk;
	}

	@Override
	public mxCodec getCodec() {
		return new mxCodec();
	}

}
